package org.sillyweasel.roo.spockaddon;

import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import com.sun.org.apache.xerces.internal.dom.ElementImpl;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.springframework.roo.classpath.PhysicalTypeDetails;
import org.springframework.roo.classpath.PhysicalTypeIdentifier;
import org.springframework.roo.classpath.PhysicalTypeMetadata;
import org.springframework.roo.classpath.PhysicalTypeMetadataProvider;
import org.springframework.roo.classpath.TypeLocationService;
import org.springframework.roo.classpath.details.MemberFindingUtils;
import org.springframework.roo.classpath.details.MutableClassOrInterfaceTypeDetails;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadataBuilder;
import org.springframework.roo.metadata.MetadataService;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.project.*;
import org.springframework.roo.support.util.Assert;
import org.springframework.roo.support.util.XmlUtils;
import org.w3c.dom.*;

import javax.naming.ConfigurationException;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

/**
 * Implementation of operations this add-on offers.
 *
 * @since 1.1
 */
@Component // Use these Apache Felix annotations to register your commands class in the Roo container
@Service
public class SpockaddonOperationsImpl implements SpockaddonOperations {
	
	/**
	 * MetadataService offers access to Roo's metadata model, use it to retrieve any available metadata by its MID
	 */
	@Reference private MetadataService metadataService;
	
	/**
	 * Use the PhysicalTypeMetadataProvider to access information about a physical type in the project
	 */
	@Reference private PhysicalTypeMetadataProvider physicalTypeMetadataProvider;
	
	/**
	 * Use ProjectOperations to install new dependencies, plugins, properties, etc into the project configuration
	 */
	@Reference private ProjectOperations projectOperations;

	/**
	 * Use TypeLocationService to find types which are annotated with a given annotation in the project
	 */
	@Reference private TypeLocationService typeLocationService;

	/** {@inheritDoc} */
	public boolean isCommandAvailable() {
		// Check if a project has been created
		return projectOperations.isProjectAvailable();
	}

	/** {@inheritDoc} */
	public void annotateType(JavaType javaType) {
		// Use Roo's Assert type for null checks
		Assert.notNull(javaType, "Java type required");

		// Retrieve metadata for the Java source type the annotation is being added to
		String id = physicalTypeMetadataProvider.getProvidesType();
		if (id == null) {
			throw new IllegalArgumentException("Cannot locate source for '" + javaType.getFullyQualifiedTypeName() + "'");
		}

		// Obtain the physical type and itd mutable details
		PhysicalTypeMetadata physicalTypeMetadata = (PhysicalTypeMetadata) metadataService.get(id);
		Assert.notNull(physicalTypeMetadata, "Java source code unavailable for type " + PhysicalTypeIdentifier.getFriendlyName(id));
		
		// Obtain physical type details for the target type
		PhysicalTypeDetails physicalTypeDetails = physicalTypeMetadata.getMemberHoldingTypeDetails();
		Assert.notNull(physicalTypeDetails, "Java source code details unavailable for type " + PhysicalTypeIdentifier.getFriendlyName(id));
		
		// Test if the type is an MutableClassOrInterfaceTypeDetails instance so the annotation can be added
		Assert.isInstanceOf(MutableClassOrInterfaceTypeDetails.class, physicalTypeDetails, "Java source code is immutable for type " + PhysicalTypeIdentifier.getFriendlyName(id));
		MutableClassOrInterfaceTypeDetails mutableTypeDetails = (MutableClassOrInterfaceTypeDetails) physicalTypeDetails;

		// Test if the annotation already exists on the target type
		if (MemberFindingUtils.getAnnotationOfType(mutableTypeDetails.getAnnotations(), new JavaType(RooSpockaddon.class.getName())) == null) {
			
			// Create JavaType instance for the add-ons trigger annotation
			JavaType rooRooSpockaddon = new JavaType(RooSpockaddon.class.getName());

			// Create Annotation metadata
			AnnotationMetadataBuilder annotationBuilder = new AnnotationMetadataBuilder(rooRooSpockaddon);
			
			// Add annotation to target type
			mutableTypeDetails.addTypeAnnotation(annotationBuilder.build());
		}
	}

	/** {@inheritDoc} */
	public void annotateAll() {
		// Use the TypeLocationService to scan project for all types with a specific annotation
		for (JavaType type: typeLocationService.findTypesWithAnnotation(new JavaType("org.springframework.roo.addon.javabean.RooJavaBean"))) {
			annotateType(type);
		}
	}
	
	/** {@inheritDoc} */
	public void setup() {
        Element configuration = XmlUtils.getConfiguration(getClass());

        installRepository(configuration);

        installDependencies(configuration);

        installBuildPlugins(configuration);

    }


    private void installBuildPlugins(Element configuration) {
        updateEclipsePlugin(projectOperations.getProjectMetadata().getBuildPlugins());

        List<Plugin> plugins = new ArrayList<Plugin>();
        for (Element pluginElement : XmlUtils.findElements("/configuration/spock/plugins/plugin", configuration)) {

            System.err.println("adding element" + pluginElement.toString());
            Plugin plugin = new Plugin(pluginElement);
            System.err.println("Executions: ");
            System.err.println(plugin.getExecutions());
            plugins.add(plugin);

            System.err.println("Plugin XML");
            System.err.println(nodeToString(pluginElement));
        }
        projectOperations.addBuildPlugins(plugins);
    }

    private void updateEclipsePlugin(Set<Plugin> buildPlugins) {
        Iterator<Plugin> pluginIterator = buildPlugins.iterator();
        while(pluginIterator.hasNext()) {
            Plugin plugin = pluginIterator.next();
            if (plugin.getArtifactId().equals("maven-eclipse-plugin")) {
                Element element = plugin.getConfiguration().getConfiguration();
                NodeList list = element.getElementsByTagName("additionalProjectNatures");
            }
        }

    }

    private void installDependencies(Element configuration) {
        List<Dependency> dependencies = new ArrayList<Dependency>();
        for (Element dependencyElement : XmlUtils.findElements("/configuration/spock/dependencies/dependency", configuration)) {
			dependencies.add(new Dependency(dependencyElement));
		}
        projectOperations.addDependencies(dependencies);
    }

    private void installRepository(Element configuration) {
        projectOperations.addRepository(
                new Repository("SillyWeasel's Spock Roo add-on repository",
                        "Spockaddon Roo add-on repository",
                        "http://sillyweasel-spock-roo-addon.googlecode.com/svn/trunk/"));
    }

    private String nodeToString(Node node) {
        TransformerFactory transFactory = TransformerFactory.newInstance();
        Transformer transformer = null;
        try {
            transformer = transFactory.newTransformer();
        } catch (TransformerConfigurationException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            return "Invalid transformer";
        }
        StringWriter buffer = new StringWriter();
        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
        try {
            transformer.transform(new DOMSource(node),
                  new StreamResult(buffer));
            return buffer.toString();
        } catch (TransformerException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            return "Error parsing : " + e.toString();
        }
    }
}